﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;

namespace Admx_Generator
{
    public class AdmxProject
    {
        private string _projectName;
        private AdmxFile _admxFile = null;
        private Dictionary<string, AdmlFile> _admlFiles = new Dictionary<string, AdmlFile>();
        private bool _haveUnsavedModifications = true;

        /// <summary>
        /// Create a new instance of the class. But don't add an Admx file.
        /// </summary>
        /// <param name="projectFolder">Folder where to create the directory for the project</param>
        /// <param name="projectName">Name of the folder for this project and name of the Admx file and Adml files</param>
        public AdmxProject(string projectFolder, string projectName)
        {
            this.ProjectFolder = Path.Combine(projectFolder, projectName);
            this.ProjectName = projectName;
        }

        #region {properties}

        /// <summary>
        /// Gets or Sets the folder name where to save all the files for this project
        /// </summary>
        public string ProjectFolder { get; private set; }

        /// <summary>
        /// Gets or Sets the name of the .admxProj file. It will be saved in <see cref="ProjectFolder"/>
        /// </summary>
        public string ProjectName
        {
            get { return this._projectName; }

            set
            {
                if (String.Compare(this._projectName, value, StringComparison.InvariantCultureIgnoreCase) != 0)
                {
                    this._projectName = value;
                    this.HaveUnsavedModifications = true;
                }
            }
        }

        public AdmxFile Admx
        {
            get { return this._admxFile; }

            private set
            {
                this._admxFile = value;
                this.HaveUnsavedModifications = true;
            }
        }

        /// <summary>
        /// Gets whether or not this Project has an Admx File
        /// </summary>
        public bool HasAdmx { get { return this.Admx != null; } }

        /// <summary>
        /// Gets or Sets if this project has some modifications that needs to be saved
        /// </summary>
        public bool HaveUnsavedModifications
        {
            get { return this._haveUnsavedModifications; }

            private set
            {
                if (this._haveUnsavedModifications != value)
                {
                    this._haveUnsavedModifications = value;
                    if (this.HaveUnsavedModificationsChanged != null)
                        this.HaveUnsavedModificationsChanged(this);
                }
            }
        }

        #endregion {properties}

        #region {methods}

        /// <summary>
        /// Add a new Language to this Project./>
        /// </summary>
        /// <param name="admlFile">AdmFile to add to the project</param>
        /// <exception cref="DuplicateLanguageException">This Exception is throw if the language is already present in the project.</exception>
        public void AddAdml(AdmlFile admlFile)
        {
            if (!this._admlFiles.ContainsKey(admlFile.Culture.ToString()))
            {
                this._admlFiles.Add(admlFile.Culture.ToString(), admlFile);
                this.HaveUnsavedModifications = true;
            }
            else
                throw new Admx_Generator.Exceptions.DuplicateLanguageException(admlFile.Culture.Name);
        }

        /// <summary>
        /// Returns a list of all languages as a list of <see cref="AdmlFile"/>
        /// </summary>
        /// <returns>A list of <see cref="AdmlFile"/> of all languages includes in this project</returns>
        public List<AdmlFile> GetAdmlList()
        {
            return this._admlFiles.Values.ToList<AdmlFile>();
        }

        /// <summary>
        /// Remove the provided <see cref="AdmlFile"/> from the list of language.
        /// </summary>
        /// <param name="admlFile"><see cref="AdmlFile"/> to remove.</param>
        public void RemoveAdml(AdmlFile admlFile)
        {
            if (this._admlFiles.ContainsKey(admlFile.Culture.ToString()))
            {
                this._admlFiles.Remove(admlFile.Culture.ToString());
                this.HaveUnsavedModifications = true;
            }
        }

        public void SetAdmx(AdmxFile admx)
        {
            this.DeleteAdmx();
            this.Admx = admx;
        }

        public void RenameAdmx(string newName)
        {
            this.Admx.Rename(newName);
            this.HaveUnsavedModifications = true;
        }

        public void DeleteAdmx()
        {
            this.Admx = null;
            foreach (AdmlFile adml in this._admlFiles.Values)
            {
                adml.Reset();
            }
        }

        /// <summary>
        /// Checks versions' consistencies then save Admx file and Adml files
        /// </summary>
        public void Save()
        {
            this.CreateFolder(this.ProjectFolder);
            if (this.Admx != null)
            {
                List<AdmlFile> mismatchingAdmlFileMajor = this.GetMajorVersionMismatchAdmlFiles(this.Admx.Resources.MinRequiredRevision.Major);

                if (mismatchingAdmlFileMajor.Count != 0)
                {
                    if (Properties.Settings.Default.AlignMajorVersion)
                    {
                        this.AlignMajorVersion(mismatchingAdmlFileMajor, this.Admx.Resources.MinRequiredRevision.Major);
                    }
                }

                List<AdmlFile> mismatchingAdmlFileMinor = this.GetMinorVersionMismatchAdmlFiles(this.Admx.Resources.MinRequiredRevision.Minor);

                if (mismatchingAdmlFileMinor.Count != 0)
                {
                    if (Properties.Settings.Default.AlignMinorVersion)
                    {
                        this.AlignMinorVersion(mismatchingAdmlFileMinor, this.Admx.Revision.Minor);
                    }
                }
                this.Admx.Save(this.ProjectFolder);

                foreach (AdmlFile adml in this._admlFiles.Values)
                {
                    adml.Save(this.ProjectFolder, this.Admx.Filename);
                }
                if (this.HaveUnsavedModifications)
                {
                    this.AutoIncrementMinorVersion();
                }
            }
            this.SaveProjectFile();
            this.HaveUnsavedModifications = false;
        }

        /// <summary>
        /// Gets a list of all Categories that refers to the specified Namespace Prefix.
        /// </summary>
        /// <param name="prefix">Prefix to search for in Categories</param>
        /// <param name="admx">The Admx where to search</param>
        /// <returns>A List of found Categories that refers to the specified Prefix</returns>
        public List<Category> GetCategoriesLinkToPrefix(string prefix, AdmxFile admx)
        {
            List<Category> result = new List<Category>();

            foreach (Category category in admx.Categories)
            {
                if (String.Compare(category.ParentCategory.NamespacePrefix, prefix, StringComparison.InvariantCultureIgnoreCase) == 0)
                    result.Add(category);
            }

            return result;
        }

        /// <summary>
        /// Gets a list of all Policies that refers to the specified Namespace Prefix.
        /// </summary>
        /// <param name="prefix">Prefix to search for in Categories</param>
        /// <param name="admx">The Admx where to search</param>
        /// <returns>A List of found Policies that refers to the specified Prefix</returns>
        public List<Policy> GetPoliciesLinkToPrefix(string prefix, AdmxFile admx)
        {
            List<Policy> result = new List<Policy>();

            foreach (Policy policy in admx.Policies)
            {
                // TODO Rechercher les SupportedOn qui font référence à prefix
                result.Add(policy);
            }

            return result;
        }

        private void AutoIncrementMinorVersion()
        {
            if (Properties.Settings.Default.AutoIncrementADMXMinorVersion)
            { this.Admx.Revision = new Revision(this.Admx.Revision.Major, this.Admx.Revision.Minor + 1); }
            if (Properties.Settings.Default.AutoIncrementADMLMinorVersion)
            {
                foreach (AdmlFile adml in this._admlFiles.Values)
                {
                    adml.Revision = new Revision(adml.Revision.Major, adml.Revision.Minor + 1);
                }
            }
        }

        private List<AdmlFile> GetMajorVersionMismatchAdmlFiles(uint admxMajorVersion)
        {
            List<AdmlFile> admlFiles = new List<AdmlFile>();

            foreach (AdmlFile adml in this._admlFiles.Values)
            {
                if (adml.Revision.Major != admxMajorVersion)
                    admlFiles.Add(adml);
            }
            return admlFiles;
        }

        private List<AdmlFile> GetMinorVersionMismatchAdmlFiles(uint admxMinorVersion)
        {
            List<AdmlFile> admlFiles = new List<AdmlFile>();

            foreach (AdmlFile adml in this._admlFiles.Values)
            {
                if (adml.Revision.Minor < admxMinorVersion)
                    admlFiles.Add(adml);
            }
            return admlFiles;
        }

        private void AlignMajorVersion(List<AdmlFile> mismatchingAdmlFiles, uint admxMajorVersion)
        {
            foreach (AdmlFile adml in mismatchingAdmlFiles)
            {
                adml.Revision = new Revision(admxMajorVersion, adml.Revision.Minor);
            }
        }

        private void AlignMinorVersion(List<AdmlFile> mismatchingAdmlFiles, uint admxMinorVersion)
        {
            foreach (AdmlFile adml in mismatchingAdmlFiles)
            {
                adml.Revision = new Revision(adml.Revision.Major, admxMinorVersion);
            }
        }

        private void CreateFolder(string fullPath)
        {
            if (!System.IO.Directory.Exists(fullPath))
            { System.IO.Directory.CreateDirectory(fullPath); }
        }

        private void SaveProjectFile()
        {
            XmlWriterSettings writerSettings = new XmlWriterSettings();
            writerSettings.Encoding = Encoding.UTF8;
            writerSettings.Indent = true;
            StreamWriter writer = new StreamWriter(Path.Combine(this.ProjectFolder, this.ProjectName + ".AdmxProj"), false, Encoding.UTF8);

            XmlWriter xmlWriter = XmlWriter.Create(writer, writerSettings);

            xmlWriter.WriteStartDocument();

            // project
            xmlWriter.WriteStartElement("project");
            xmlWriter.WriteAttributeString("Name", this.ProjectName);

            // admx
            xmlWriter.WriteStartElement("admx");
            xmlWriter.WriteAttributeString("Name", this.Admx != null ? this.Admx.Filename : String.Empty);
            if (this.Admx != null && this.Admx.UsedNamespaces.Count != 0)
            {
                xmlWriter.WriteStartElement("usedNamespaces");
                foreach (Namespace usedNamespace in this.Admx.UsedNamespaces)
                {
                    // namespace
                    xmlWriter.WriteStartElement("namespace");
                    xmlWriter.WriteAttributeString("prefix", usedNamespace.Prefix);
                    xmlWriter.WriteAttributeString("target", usedNamespace.Target);
                    xmlWriter.WriteAttributeString("filename", usedNamespace.FileName);
                    xmlWriter.WriteEndElement(); // namespace
                }
                xmlWriter.WriteEndElement(); // usedNamespaces
            }

            xmlWriter.WriteEndElement(); // admx

            xmlWriter.WriteEndElement(); // project
            xmlWriter.WriteEndDocument();
            xmlWriter.Flush();
            writer.Flush();
            writer.Close();
        }

        public override string ToString()
        {
            return this.ProjectName;
        }

        #endregion {methods}

        #region (public events)

        public delegate void HaveUnsavedModificationsEventHandler(AdmxProject sender);
        /// <summary>
        /// This event is fire each time the property 'HaveUnsavedModifications' is changed.
        /// </summary>
        internal event HaveUnsavedModificationsEventHandler HaveUnsavedModificationsChanged;


        #endregion (public events)
    }
}
